package ltd.helper;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
/**
 * RSA算法助手类
 * RSA算法规定：
 * 加密的最大长度：keysize / 8 - 11，如：1024 / 8 - 11 = 117 个字节；
 * 解密最大长度：keysize / 8，如：1024 / 8 = 128;
 * 所以，如果要加密较长的数据，可以采用数据截取的方法，分段加密。
 * Created by chinalin on 2017/3/31.
 */
public class RSAHelper {
    private static final Logger LOGGER = LoggerFactory.getLogger("RSAHelper.class") ;

    /** 公钥 */
    public static RSAPublicKey PUBLIC_KEY = null;
    /** 私钥 */
    public static RSAPrivateKey PRIVATE_KEY = null;

    public static final Charset CHARSET = Charset.forName("UTF-8");

    /** BASE64解密 */
    public static byte[] decryptBASE64(String key) throws Exception {
        return (new BASE64Decoder()).decodeBuffer(key);
    }

    /** BASE64加密 */
    public static String encryptBASE64(byte[] key) throws Exception {
        return (new BASE64Encoder()).encodeBuffer(key);
    }

    /**
     * 得到公钥
     * @param key 密钥字符串（经过base64编码）
     */
    public static RSAPublicKey getPublicKey(String key) throws Exception {
        byte[] keyBytes;
        keyBytes = (new BASE64Decoder()).decodeBuffer(key);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        RSAPublicKey publicKey = (RSAPublicKey)keyFactory.generatePublic(keySpec);
        return publicKey;
    }
    /**
     * 生成公钥
     * @param modulus 模数
     * @param publicExponent 指数
     * @return
     */
    public static RSAPublicKey getPublicKey(byte[] modulus, byte[] publicExponent) throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(new BigInteger(modulus), new BigInteger(publicExponent));
        RSAPublicKey publicKey = (RSAPublicKey)keyFactory.generatePublic(pubKeySpec);
        return publicKey;
    }
    /**
     * 生成公钥
     * @param modulus 模数
     * @param publicExponent 指数
     * @return
     */
    public static RSAPublicKey getPublicKey(String modulus, String publicExponent) throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(new BigInteger(modulus), new BigInteger(publicExponent));
        RSAPublicKey publicKey = (RSAPublicKey)keyFactory.generatePublic(pubKeySpec);
        return publicKey;
    }

    /**
     * 得到私钥
     * @param key 密钥字符串（经过base64编码）
     */
    public static RSAPrivateKey getPrivateKey(String key) throws Exception {
        byte[] keyBytes;
        keyBytes = (new BASE64Decoder()).decodeBuffer(key);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
        return privateKey;
    }
    /**
     * 生成私钥
     * @param modulus 模数
     * @param privateExponent 指数
     * @return
     */
    public static RSAPrivateKey getPrivateKey(byte[] modulus, byte[] privateExponent) throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        RSAPrivateKeySpec priKeySpec = new RSAPrivateKeySpec(new BigInteger(modulus), new BigInteger(privateExponent));
        RSAPrivateKey publicKey = (RSAPrivateKey)keyFactory.generatePublic(priKeySpec);
        return publicKey;
    }
    /**
     * 生成私钥
     * @param modulus 模数
     * @param privateExponent 指数
     * @return
     */
    public static RSAPrivateKey getPrivateKey(String modulus, String privateExponent) throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        RSAPrivateKeySpec priKeySpec = new RSAPrivateKeySpec(new BigInteger(modulus), new BigInteger(privateExponent));
        RSAPrivateKey publicKey = (RSAPrivateKey)keyFactory.generatePublic(priKeySpec);
        return publicKey;
    }

    /**
     * 得到密钥字符串（经过base64编码）
     * @return
     */
    public static String getKeyString(Key key) throws Exception {
        byte[] keyBytes = key.getEncoded();
        String s = (new BASE64Encoder()).encode(keyBytes);
        return s;
    }

    public static String getKeyBase64(RSAPublicKey key) throws Exception {
        byte[] keyBytes = key.getModulus().toByteArray();
        String s = (new BASE64Encoder()).encode(keyBytes);
        return s;
    }

    public static int getKeySize(BigInteger modulus){
        try {
            int len = modulus.toString(2).length(); // 转换为二进制，获取公钥长度
            //LOGGER.info("长度：" + len);
            return len;
        } catch (Exception e) {
            LOGGER.error("### 获取密钥的KeySize出错 ###", e);
        }
        return 0;
    }

    /**
     * 初始化 RSA 密钥，默认为1024位
     */
    public static void initKey() {
        initKey(1024);
    }

    /**
     * 初始化 RSA 密钥
     * @param keyNum 密钥位数
     */
    public static void initKey(int keyNum) {
        try {
            KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
            // 密钥位数
            keyPairGen.initialize(keyNum);
            // 密钥对
            KeyPair keyPair = keyPairGen.generateKeyPair();
            // 公钥
            PUBLIC_KEY = (RSAPublicKey) keyPair.getPublic();
            // 私钥
            PRIVATE_KEY = (RSAPrivateKey) keyPair.getPrivate();
        } catch (Exception e) {
            LOGGER.error("!!! RSA 密钥生成失败...",e);
        }
    }

    /**
     * 执行 RSA 加密处理，使用 公钥 加密
     * @param str 要进行加密的明文
     * @return 加密后的字节数组
     */
    public static byte[] doRSAEncoder(String str) {
        try {
            return doRSAEncoder(str.getBytes("UTF-8"));
        } catch (Exception e) {
            LOGGER.error("!!! RSA 加密失败...",e);
        }
        return null;
    }

    /**
     * 执行 RSA 加密处理，使用指定的 公钥 加密
     * @param publicKey 指定的公钥
     * @param str 要进行加密的明文
     * @return 加密后的字节数组
     */
    public static byte[] doRSAEncoder(String str, RSAPublicKey publicKey) {
        byte[] enbyte;
        try {
            enbyte = str.getBytes("UTF-8");
        } catch (Exception e) {
            enbyte = str.getBytes();
        }
        return doRSAEncoder(enbyte, publicKey);
    }

    /**
     * 执行 RSA 加密处理，使用 公钥 加密
     * @param enbyte 要进行加密的明文字节数组
     * @return 加密后的字节数组
     */
    public static byte[] doRSAEncoder(byte[] enbyte) {
        return doRSAEncoder(enbyte, PUBLIC_KEY);
    }

    /**
     * 执行 RSA 加密处理，使用指定的 公钥 加密
     * @param publicKey 指定的公钥
     * @param enbyte 要进行加密的明文字节数组
     * @return 加密后的字节数组
     */
    public static byte[] doRSAEncoder(byte[] enbyte, RSAPublicKey publicKey) {
        try {
            // 加解密类
            Cipher cipher = Cipher.getInstance("RSA");
            // 加密
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            int keySize = getKeySize(publicKey.getModulus());
            int enSize = keySize / 8 - 11;
            if(enbyte.length <= enSize){
                return cipher.doFinal(enbyte);
            }else{
                int outputSize = cipher.getOutputSize(enbyte.length);//获得加密块加密后块大小 128
                int nm = (int)Math.ceil(enbyte.length * 1.0 / enSize);
                byte[] raw = new byte[outputSize * nm];//加密后的字数组
                for (int i = 0; i < nm; i++) {
                    if(i < nm - 1){
                        cipher.doFinal(enbyte, i * enSize, enSize, raw, i * outputSize);
                    }else{
                        cipher.doFinal(enbyte, i * enSize, enbyte.length - i * enSize, raw, i * outputSize);
                    }
                }
                return raw;
            }
        } catch (Exception e) {
            LOGGER.error("!!! 【公钥】 加密处理出错...",e);
        }
        return null;
    }

    /**
     * 执行 RSA 加密处理，使用 私钥 加密
     * @param str 要进行加密的明文
     * @return 加密后的字节数组
     */
    public static byte[] doRSAEncoderPri(String str) {
        byte[] enbyte;
        try {
            enbyte = str.getBytes("UTF-8");
        } catch (Exception e) {
            enbyte = str.getBytes();
        }
        return doRSAEncoderPri(enbyte, PRIVATE_KEY);
    }

    /**
     * 执行 RSA 加密处理，使用指定的 私钥 加密
     * @param privateKey 指定的私钥
     * @param str 要进行加密的明文
     * @return 加密后的字节数组
     */
    public static byte[] doRSAEncoderPri(String str, RSAPrivateKey privateKey) {
        byte[] enbyte;
        try {
            enbyte = str.getBytes("UTF-8");
        } catch (Exception e) {
            enbyte = str.getBytes();
        }
        return doRSAEncoderPri(enbyte, privateKey);
    }

    /**
     * 执行 RSA 加密处理，使用指定的 私钥 加密
     * @param enbyte 要进行加密的明文字节数组
     * @return 加密后的字节数组
     */
    public static byte[] doRSAEncoderPri(byte[] enbyte) {
        return doRSAEncoderPri(enbyte, PRIVATE_KEY);
    }

    /**
     * 执行 RSA 加密处理，使用指定的 私钥 加密
     * @param privateKey 指定的私钥
     * @param enbyte 要进行加密的明文字节数组
     * @return 加密后的字节数组
     */
    public static byte[] doRSAEncoderPri(byte[] enbyte, RSAPrivateKey privateKey) {
        try {
            // 加解密类
            Cipher cipher = Cipher.getInstance("RSA");
            // 加密
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);
            int keySize = getKeySize(privateKey.getModulus());
            int enSize = keySize / 8 - 11;
            if(enbyte.length <= enSize){
                return cipher.doFinal(enbyte);
            }else{
                int outputSize = cipher.getOutputSize(enbyte.length);//获得加密块加密后块大小 128
                int nm = (int)Math.ceil(enbyte.length * 1.0 / enSize);
                byte[] raw = new byte[outputSize * nm];//加密后的字数组
                for (int i = 0; i < nm; i++) {
                    if(i < nm - 1){
                        cipher.doFinal(enbyte, i * enSize, enSize, raw, i * outputSize);
                    }else{
                        cipher.doFinal(enbyte, i * enSize, enbyte.length - i * enSize, raw, i * outputSize);
                    }
                }
                return raw;
            }
        } catch (Exception e) {
            LOGGER.error("!!! 【私钥】 加密处理出错...",e);
        }
        return null;
    }

    /**
     * 执行 RSA 解密处理，使用 私钥 解密
     * @param debyte 要进行解密的字节数组
     * @return 解密后的 字符
     */
    public static String doRSADecoder(byte[] debyte) {
        return doRSADecoder(debyte, PRIVATE_KEY);
    }

    /**
     * 执行 RSA 解密处理，使用指定的 私钥 解密
     * @param privateKey 指定的 私钥
     * @param debyte 要进行解密的字节数组
     * @return 解密后的 字符
     */
    public static String doRSADecoder(byte[] debyte, RSAPrivateKey privateKey) {
        try {
            // 加解密类
            Cipher cipher = Cipher.getInstance("RSA");
            // 解密
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            int keySize = getKeySize(privateKey.getModulus());
            int deSize = keySize / 8;
            if(debyte.length <= deSize){
                byte[] deBytes = cipher.doFinal(debyte);
                return new String(deBytes, CHARSET);
            } else {
                int nm = (int) Math.ceil(debyte.length * 1.0 / deSize);
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                for (int i = 0; i < nm; i++) {
                    bos.write(cipher.doFinal(debyte, i * deSize, deSize));
                }
                return bos.toString("UTF-8");
            }
        } catch (Exception e) {
            LOGGER.error("!!! 【私钥】 解密处理出错...",e);
        }
        return null;
    }

    /**
     * 执行 RSA 解密处理，使用 公钥 解密
     * @param debyte 要进行解密的字节数组
     * @return 解密后的 字符
     */
    public static String doRSADecoderPub(byte[] debyte) {
        return doRSADecoderPub(debyte, PUBLIC_KEY);
    }

    /**
     * 执行 RSA 解密处理，使用指定的 公钥 解密
     * @param publicKey 指定的公钥
     * @param debyte 要进行解密的字节数组
     * @return 解密后的 字符
     */
    public static String doRSADecoderPub(byte[] debyte, RSAPublicKey publicKey) {
        try {
            // 加解密类
            Cipher cipher = Cipher.getInstance("RSA");
            // 解密
            cipher.init(Cipher.DECRYPT_MODE, publicKey);
            int keySize = getKeySize(publicKey.getModulus());
            int deSize = keySize / 8;
            if(debyte.length <= deSize){
                byte[] deBytes = cipher.doFinal(debyte);
                return new String(deBytes, CHARSET);
            } else {
                int nm = (int) Math.ceil(debyte.length * 1.0 / deSize);
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                for (int i = 0; i < nm; i++) {
                    bos.write(cipher.doFinal(debyte, i * deSize, deSize));
                }
                return bos.toString("UTF-8");
            }
        } catch (Exception e) {
            LOGGER.error("!!! 【公钥】 解密处理出错...",e);
        }
        return null;
    }

    /**
     * 用 私钥 对加密码后的数据 生成数字签名
     * @param data 加密后的数据
     * @return 用私钥生成的 数字签名 字节数组
     */
    public static byte[] doSign(byte[] data){
        return doSign(data, PRIVATE_KEY);
    }

    /**
     * 用 私钥 对加密码后的数据 生成数字签名
     * @param data 加密后的数据
     * @param privateKey 指定的私钥
     * @return 用私钥生成的 数字签名 字节数组
     */
    public static byte[] doSign(byte[] data, RSAPrivateKey privateKey){
        try {
            Signature signature = Signature.getInstance("MD5withRSA");
            signature.initSign(privateKey);
            signature.update(data);
            return signature.sign();
        } catch (Exception e) {
            LOGGER.error("!!! 生成数字签名错误...",e);
        }
        return null;
    }

    /**
     * 用 公钥 验证加密数据的数字签名
     * @param data 加密数据的字节数组
     * @param sign 数字签名 字节数组
     * @return 是否验证成功
     */
    public static boolean doVerify(byte[] data, byte[] sign){
        return doVerify(data, PUBLIC_KEY, sign);
    }

    /**
     * 用 公钥 验证加密数据的数字签名
     * @param data 加密数据的字节数组
     * @param publicKey 指定的 公钥
     * @param sign 数字签名 字节数组
     * @return 是否验证成功
     */
    public static boolean doVerify(byte[] data, RSAPublicKey publicKey, byte[] sign){
        try {
            Signature signature = Signature.getInstance("MD5withRSA");
            signature.initVerify(publicKey);
            signature.update(data);
            return signature.verify(sign);
        } catch (Exception e) {
            LOGGER.error("!!! 验证数字签名错误...",e);
        }
        return false;
    }

}
